package models

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"
	"unicode/utf8"

	"gorm.io/gorm"
)

const (
	Directory = "M"
	CaiDan    = "C"
	Button    = "F"
	Api       = "A"
)

type Menu struct {
	Model
	MenuName   *string `json:"menuName" gorm:"column:menu_name"`
	Title      string  `json:"title" gorm:"column:title"`
	Icon       string  `json:"icon" gorm:"column:icon"`
	Path       *string `json:"path" gorm:"column:path"`
	Paths      string  `json:"paths" gorm:"column:paths"`
	MenuType   string  `json:"menuType" gorm:"column:menu_type"`
	Action     *string `json:"action" gorm:"column:action"`
	Permission *string `json:"permission" gorm:"column:permission"`
	ParentId   int     `json:"parentId" gorm:"column:parent_id"`
	NoCache    *bool   `json:"noCache" gorm:"column:no_cache"`
	Breadcrumb *string `json:"breadcrumb" gorm:"column:breadcrumb"`
	Component  *string `json:"component" gorm:"column:component"`
	Sort       int     `json:"sort" gorm:"column:sort"`
	Visible    *int    `json:"visible" gorm:"column:visible"`
	IsFrame    *int    `json:"isFrame" gorm:"column:is_frame"`
	DataScope  string  `json:"dataScope" gorm:"-"`
	Params     string  `json:"params" gorm:"-"`
	RoleId     int     `gorm:"-"`
	Children   []Menu  `json:"children" gorm:"-"`
	IsSelect   bool    `json:"is_select" gorm:"-"`
}

func (Menu) TableName() string {
	return "menu"
}

type MenuLable struct {
	Id       uint64      `json:"id" db:"-"`
	Label    string      `json:"label" db:"-"`
	Children []MenuLable `json:"children" db:"-"`
}

//根据role_key获取menu
func GetMenuByRoleKey(ctx context.Context, roleKey string) ([]Menu, error) {
	var m []Menu
	res := db(ctx).
		Table("menu").
		Joins("left join `role_menu` on `role_menu`.menu_id = menu.id").
		Select("`menu`.*").
		Where("`role_menu`.role_name=? and menu_type in ('M','C')", roleKey).Order("sort").Find(&m)
	if res.Error != nil {
		return nil, res.Error
	}
	mm := make([]Menu, 0)
	if len(m) > 0 {
		for i := 0; i < len(m); i++ {
			if m[i].ParentId != 0 {
				continue
			}
			menusInfo := DiguiMenu(&m, m[i])
			mm = append(mm, menusInfo)
		}
	}
	return mm, nil
}

func DiguiMenu(menulist *[]Menu, menu Menu) Menu {
	list := *menulist

	min := make([]Menu, 0)
	for j := 0; j < len(list); j++ {

		if int(menu.ID) != list[j].ParentId {
			continue
		}
		mi := Menu{}
		mi.ID = list[j].ID
		mi.MenuName = list[j].MenuName
		mi.Title = list[j].Title
		mi.Icon = list[j].Icon
		mi.Path = list[j].Path
		mi.MenuType = list[j].MenuType
		mi.Action = list[j].Action
		mi.Permission = list[j].Permission
		mi.ParentId = list[j].ParentId
		mi.NoCache = list[j].NoCache
		mi.Breadcrumb = list[j].Breadcrumb
		mi.Component = list[j].Component
		mi.Sort = list[j].Sort
		mi.Visible = list[j].Visible
		mi.CreatedAt = list[j].CreatedAt
		mi.Children = []Menu{}

		if mi.MenuType != "F" {
			ms := DiguiMenu(menulist, mi)
			min = append(min, ms)

		} else {
			min = append(min, mi)
		}

	}
	menu.Children = min
	return menu
}

//根据菜单ID获取菜单
func GetMenuByIDs(tx *gorm.DB, menuIDs []int) ([]Menu, error) {
	var menus []Menu
	res := tx.Table("menu").Where("id in (?)", menuIDs).Find(&menus)
	if res.Error != nil {
		return nil, res.Error
	}
	return menus, nil
}

func GetMenuTree(ctx context.Context) ([]*MenuLable, error) {
	var m []*Menu
	if res := db(ctx).Table("menu").Find(&m); res.Error != nil {
		return nil, res.Error
	}
	mm := make([]*MenuLable, 0)
	for i := 0; i < len(m); i++ {
		if m[i].ParentId != 0 {
			continue
		}
		e := MenuLable{}
		e.Id = m[i].ID
		e.Label = m[i].Title
		menusInfo := DiguiMenuLable(m, e)

		mm = append(mm, &menusInfo)
	}
	return mm, nil
}

func DiguiMenuLable(menulist []*Menu, menu MenuLable) MenuLable {
	min := make([]MenuLable, 0)
	for j := 0; j < len(menulist); j++ {
		if int(menu.Id) != menulist[j].ParentId {
			continue
		}
		mi := MenuLable{}
		mi.Id = menulist[j].ID
		mi.Label = menulist[j].Title
		mi.Children = []MenuLable{}
		if menulist[j].MenuType != "F" {
			ms := DiguiMenuLable(menulist, mi)
			min = append(min, ms)
		} else {
			min = append(min, mi)
		}
	}
	menu.Children = min
	return menu
}

func GetMenuList(ctx context.Context, title string, visible *int) ([]Menu, error) {
	db := db(ctx).Table("menu")
	if title != "" {
		db = db.Where("title like %?%", title)
	}
	if visible != nil {
		db = db.Where("visible = ?", *visible)
	}
	var m []Menu
	if res := db.Find(&m); res.Error != nil {
		return nil, res.Error
	}
	mm := make([]Menu, 0)
	for i := 0; i < len(m); i++ {
		if m[i].ParentId != 0 {
			continue
		}
		menusInfo := DiguiMenu(&m, m[i])

		mm = append(mm, menusInfo)
	}
	return mm, nil
}

func GetMenuByID(ctx context.Context, id int64) (*Menu, error) {
	r := new(Menu)
	res := db(ctx).Where("id = ?", id).First(r)
	return r, checkErr(res.Error)
}

func AddMenu(ctx context.Context, request *Menu) (err error) {
	now := time.Now().Unix()
	var res *gorm.DB
	switch request.MenuType {
	case Directory:
		if request.MenuName == nil || request.Component == nil || request.Path == nil ||
			request.IsFrame == nil || request.Visible == nil {
			return errors.New("参数错误")
		}
		sqlStr := "insert into `menu` (`icon`,`component`,`path`,`menu_type`," +
			"`menu_name`,`is_frame`,`parent_id`,`sort`,`title`,`visible`,`created_at`," +
			"`updated_at`) values (?,?,?,?,?,?,?,?,?,?,?,?)"
		res = db(ctx).Exec(sqlStr, request.Icon, request.Component, request.Path,
			request.MenuType, request.MenuName, request.IsFrame, request.ParentId,
			request.Sort, request.Title, request.Visible, now, now)
	case CaiDan:
		if request.MenuName == nil || request.Component == nil || request.Path == nil || request.Permission == nil ||
			request.IsFrame == nil || request.Visible == nil {
			return errors.New("参数错误")
		}
		sqlStr := "insert into `menu` (`icon`,`component`,`path`,`menu_type`," +
			"`menu_name`,`is_frame`,`parent_id`,`sort`,`title`,`visible`,`permission`,`created_at`," +
			"`updated_at`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)"
		res = db(ctx).Exec(sqlStr, request.Icon, request.Component, request.Path,
			request.MenuType, request.MenuName, request.IsFrame, request.ParentId,
			request.Sort, request.Title, request.Visible, request.Permission, now, now)
	case Button:
		if request.Permission == nil {
			return errors.New("参数错误")
		}
		sqlStr := "insert into `menu` (`icon`,`menu_type`,`parent_id`,`sort`," +
			"`title`,`visible`,`permission`,`created_at`,`updated_at`) values (?,?,?,?,?,?,?,?,?)"
		res = db(ctx).Exec(sqlStr, request.Icon, request.MenuType, request.ParentId,
			request.Sort, request.Title, request.Visible, request.Permission, now, now)
	case Api:
		if request.Visible == nil || request.Action == nil || request.Path == nil {
			return errors.New("参数错误")
		}
		sqlStr := "insert into `menu` (`icon`,`menu_type`," +
			"`action`,`parent_id`,`sort`,`title`,`visible`,`path`,`created_at`,`updated_at`) values (?,?,?,?,?,?,?,?,?,?)"
		res = db(ctx).Exec(sqlStr, request.Icon,
			request.MenuType, request.Action, request.ParentId,
			request.Sort, request.Title, request.Visible, request.Path, now, now)
	default:
		return errors.New("参数错误")
	}
	if res.Error != nil {
		return res.Error
	}
	var r Menu
	res.Scan(&r)
	if r.ID == 0 {
		return errors.New("server error")
	}
	return getPaths(nil, int64(r.ID), request.ParentId)
}

func getPaths(tx *gorm.DB, menuID int64, parentId int) (err error) {
	var paths string
	parentMenu := new(Menu)
	if parentId != 0 {
		parentMenu, err = GetMenuByID(baseCtx, int64(parentId))
		if err != nil {
			return err
		}
		if parentMenu.Paths == "" {
			err = errors.New("父级paths异常，请尝试对当前节点父级菜单进行更新操作！")
			return
		}
		paths = parentMenu.Paths + "/" + fmt.Sprintf("%d", menuID)
	} else {
		paths = "/0/" + fmt.Sprintf("%d", menuID)
	}
	return UpdateMenuPaths(tx, menuID, paths)
}

func UpdateMenuPaths(tx *gorm.DB, id int64, paths string) error {
	var d *gorm.DB
	if tx != nil {
		d = tx
	} else {
		d = db(baseCtx)
	}
	res := d.Table("menu").Where("id = ?", id).Update("paths", paths)
	return res.Error
}

func GetRightMenu(m []Menu, id int64) *Menu {
	for _, menu := range m {
		mm := menu
		if int64(menu.ID) == id {
			return &mm
		}
		if len(mm.Children) > 0 {
			mmm := GetRightMenu(mm.Children, id)
			if mmm != nil {
				return mmm
			}
		}
	}
	return nil
}

func GetMenuParam(m []Menu) ([]uint64, []string) {
	ids := []uint64{}
	paths := []string{}
	for _, menu := range m {
		ids = append(ids, menu.ID)
		if menu.Path != nil && utf8.RuneCountInString(strings.Trim(*menu.Path, " ")) > 0 {
			paths = append(paths, *menu.Path)
		}
		if len(menu.Children) > 0 {
			id, p := GetMenuParam(menu.Children)
			ids = append(ids, id...)
			paths = append(paths, p...)
		}
	}
	return ids, paths
}

func DeleteMenuByID(tx *gorm.DB, ids []uint64) error {
	return tx.Table("menu").Where("id in (?)", ids).Delete(&Menu{}).Error
}

func UpdateMenu(tx *gorm.DB, request *Menu) (err error) {
	now := time.Now().Unix()
	var res *gorm.DB
	switch request.MenuType {
	case Directory:
		if request.MenuName == nil || request.Component == nil || request.Path == nil ||
			request.IsFrame == nil || request.Visible == nil {
			return errors.New("参数错误")
		}
		sqlStr := "update `menu` set `icon` = ?,`component`=?,`path`=?,`menu_type`=?," +
			"`menu_name`=?,`is_frame`=?,`parent_id`=?,`sort`=?,`title`=?,`visible`=?,`updated_at` = ? where id = ?"
		res = tx.Exec(sqlStr, request.Icon, request.Component, request.Path,
			request.MenuType, request.MenuName, request.IsFrame, request.ParentId,
			request.Sort, request.Title, request.Visible, now, request.ID)
	case CaiDan:
		if request.MenuName == nil || request.Component == nil || request.Path == nil || request.Permission == nil ||
			request.IsFrame == nil || request.Visible == nil {
			return errors.New("参数错误")
		}
		sqlStr := "update `menu` set `icon`= ?,`component`= ?,`path`= ?,`menu_type`= ?," +
			"`menu_name`= ?,`is_frame`= ?,`parent_id`= ?,`sort`= ?,`title`= ?,`visible`= ?,`permission`= ?,`updated_at` = ? where id = ?"
		res = tx.Exec(sqlStr, request.Icon, request.Component, request.Path,
			request.MenuType, request.MenuName, request.IsFrame, request.ParentId,
			request.Sort, request.Title, request.Visible, request.Permission, now, request.ID)
	case Button:
		if request.Permission == nil {
			return errors.New("参数错误")
		}
		sqlStr := "update `menu` set `icon`= ?,`menu_type`= ?,`parent_id`= ?,`sort`= ?,`title`= ?,`visible`= ?,`updated_at` = ? where id = ?"
		res = tx.Exec(sqlStr, request.Icon, request.MenuType, request.ParentId,
			request.Sort, request.Title, request.Visible, now, request.ID)
	case Api:
		if request.Visible == nil || request.Action == nil || request.Path == nil {
			return errors.New("参数错误")
		}
		sqlStr := "update `menu` set `icon`= ?,`menu_type`= ?," +
			"`action`= ?,`parent_id`= ?,`sort`= ?,`title`= ?,`path` = ?,`visible`= ?,`updated_at` = ? where id =?"
		res = tx.Exec(sqlStr, request.Icon,
			request.MenuType, request.Action, request.ParentId,
			request.Sort, request.Title, request.Path, request.Visible, now, request.ID)
	default:
		return errors.New("参数错误")
	}
	if res.Error != nil {
		return res.Error
	}
	return getPaths(tx, int64(request.ID), request.ParentId)
}
